package app.pool.storage;

import app.pool.utils.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class StorageMethods {

    private static Storage storage = new Storage();

    public static void setBaseDir(File dir) {
        storage.setBaseDir(dir);
    }

    public static boolean existEntry(EntryInfo entry) {
        return storage.existEntry(entry);
    }

    public static void storeFile(FileData file) throws IOException {
        storage.storeFile(file);
    }

    public static String[] listFileNames() {
        return storage.listFileNames();
    }

    public static File appFile(String name) {
        return storage.appFile(name);
    }

    static class Storage {

        private static Logger logger = LoggerFactory.getLogger(Storage.class);

        private File baseDir;

        private Set<CacheEntryInfo> cacheEntryInfos = Collections.synchronizedSet(new HashSet<CacheEntryInfo>());

        public void setBaseDir(File baseDir) {
            this.baseDir = baseDir;
            this.tmpDir().mkdir();
            this.cacheDir().mkdir();
            this.appDir().mkdir();

            for (File file : this.cacheDir().listFiles()) {
                if (CacheEntryInfo.isCachefile(file.getName())) {
                    CacheEntryInfo entryInfo = new CacheEntryInfo();
                    String[] items = file.getName().split("_");
                    int length = Integer.parseInt(items[0]);
                    if (length == file.length()) {
                        entryInfo.setMd5(items[1]);
                        entryInfo.setLength(length);
                        this.cacheEntryInfos.add(entryInfo);
                    }
                }
            }

        }

        private File appDir() {
            return new File(this.baseDir, "app");
        }

        private File cacheDir() {
            return new File(this.baseDir, "cache");
        }

        private File tmpDir() {
            return new File(this.baseDir, "tmp");
        }

        public boolean existEntry(EntryInfo entryInfo) {
            return cacheEntryInfos.contains(entryInfo);
        }

        public void storeFile(FileData file) throws IOException {

            byte[] uploadfilecontent = IOUtils.toByteArray(file.getFile().getInputStream());
            List<EntryInfo> existedEntryInfos = JSONUtils.toList(file.getEntryInfos(), EntryInfo.class);
            File tmpTargetFile = createTargetFile(uploadfilecontent, existedEntryInfos);
            String targetMd5 = MD5Utils.getMD5(FileUtils.readFileToByteArray(tmpTargetFile));
            logger.info("上传的文件MD5=" + file.getMd5() + ",恢复后文件MD5=" + targetMd5);

            if (!file.getMd5().equals(targetMd5)) {
                throw new RuntimeException("文件恢复失败,MD5值不相等.上传的文件MD5=" + file.getMd5() + ",恢复后文件MD5=" + targetMd5);
            }

            cacheBigEntry(tmpTargetFile, existedEntryInfos);
            File dest = new File(this.appDir(), file.getFile().getOriginalFilename());
            if (dest.exists()) {
                if (!dest.delete()) {
                    throw new RuntimeException("删除旧文件失败");
                }
            }

            if (!tmpTargetFile.renameTo(dest)) {
                logger.error("文件重命名失败" + tmpTargetFile.getAbsolutePath() + "->" + dest.getAbsolutePath());
                throw new RuntimeException("文件重命名失败");
            }
            logger.info("上传的文件MD5=" + file.getMd5() + ",存储到" + dest + ",MD5=" + targetMd5);
        }

        private void cacheBigEntry(File file, List<EntryInfo> existedEntryInfos) {
            try (ZipFile zipSrc = new ZipFile(file);) {

                Enumeration srcEntries = zipSrc.entries();
                while (srcEntries.hasMoreElements()) {
                    ZipEntry entry = (ZipEntry) srcEntries.nextElement();
                    if (EntryInfo.findByName(existedEntryInfos, entry.getName()) == null) {
                        byte[] content = IOUtils.toByteArray(zipSrc.getInputStream(entry));

                        if (content.length > 1024 * 100) {
                            createCache(content);
                        }
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private File createTargetFile(byte[] uploadfilecontent, List<EntryInfo> entryInfos) {

            try {

                List<FileContentUtils.FilePart> filePartList = new ArrayList<>();

                for (EntryInfo entryInfo : entryInfos) {
                    File md5File = getCacheFile(entryInfo);
                    if (!md5File.exists()) {
                        logger.error("找不到对应的文件" + md5File.getAbsolutePath());
                        throw new FileNotFoundException(md5File.getName());
                    }

                    FileContentUtils.FilePart filePart = new FileContentUtils.FilePart();
                    filePart.setData(FileUtils.readFileToByteArray(md5File));
                    filePart.setStart(entryInfo.getStart());
                    filePart.setLength(entryInfo.getLength());
                    filePartList.add(filePart);
                }

                byte[] newfilecontent = FileContentUtils.restoreDeletedFileContent(uploadfilecontent, new FileContentUtils.FileParts(filePartList));
                File targetFile = new File(this.tmpDir(), UUID.randomUUID().toString());
                FileUtils.writeByteArrayToFile(targetFile, newfilecontent);

                return targetFile;

            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }

        private File getTempCacheFile(CacheEntryInfo cacheEntryInfo) {
            return new File(this.cacheDir(), cacheEntryInfo.tmpCacheFilename());
        }

        private File getCacheFile(CacheEntryInfo cacheEntryInfo) {
            return new File(this.cacheDir(), cacheEntryInfo.cacheFilename());
        }

        private void createCache(byte[] content) throws IOException {
            String md5 = MD5Utils.getMD5(content);
            CacheEntryInfo cacheEntryInfo = new CacheEntryInfo();
            cacheEntryInfo.setMd5(md5);
            cacheEntryInfo.setLength(content.length);

            File tmpCacheFile = getTempCacheFile(cacheEntryInfo);
            FileUtils.writeByteArrayToFile(tmpCacheFile, content);

            File cacheFile = getCacheFile(cacheEntryInfo);
            if (tmpCacheFile.renameTo(cacheFile)) {

                logger.info("建立cache文件" + tmpCacheFile);

                CacheEntryInfo entryInfo = new CacheEntryInfo();
                entryInfo.setMd5(md5);
                entryInfo.setLength(content.length);
                this.cacheEntryInfos.add(entryInfo);
            } else {
                tmpCacheFile.delete();
            }
        }

        public String[] listFileNames() {
            return this.appDir().list();
        }

        public File appFile(String name) {
            return new File(this.appDir(), name);
        }

    }

}
